module cmsdk_ahb_to_apb #(
	// Parameter to define address width
	// 16 = 2^16 = 64KB APB address space
	parameter   ADDRWIDTH = 16,
	parameter   REGISTER_RDATA = 1,
	parameter	REGISTER_WDATA = 0)
	(
	// --------------------------------------------------------------------------
	// Port Definitions
	// --------------------------------------------------------------------------
	input 	wire                 	HCLK,      	// Clock
	input 	wire                 	HRESETn,   	// Reset
	input 	wire                 	PCLKEN,    	// APB clock enable signal
			
	input 	wire                 	HSEL,      	// Device select
	input	wire [ADDRWIDTH-1:0]	HADDR,     	// Address
	input 	wire [1:0] 				HTRANS,    	// Transfer control
	input 	wire [2:0] 				HSIZE,     	// Transfer size
	input 	wire [3:0] 				HPROT,     	// Protection control
	input 	wire                 	HWRITE,    	// Write control
	input 	wire                 	HREADY,    	// Transfer phase done
	input 	wire [31:0] 			HWDATA,    	// Write data
			
	output	reg                  	HREADYOUT, 	// Device ready
	output	wire [31:0] 			HRDATA,    	// Read data output
	output	wire                 	HRESP,     	// Device response
												// APB Output
	output	wire [ADDRWIDTH-1:0] 	PADDR,     	// APB Address
	output	wire                 	PENABLE,   	// APB Enable
	output	wire                 	PWRITE,    	// APB Write
	output	wire [3:0] 				PSTRB,     	// APB Byte Strobe
	output	wire [2:0] 				PPROT,     	// APB Prot
	output	wire [31:0] 			PWDATA,    	// APB write data
	output	wire                 	PSEL,      	// APB Select
			
	output	wire                 	APBACTIVE, 	// APB bus is active, for clock gating
												// of APB bus
		
												// APB Input
	input 	wire [31:0] 			PRDATA,    	// Read data for each APB slave
	input 	wire                 	PREADY,    	// Ready for each APB slave
	input 	wire                 	PSLVERR);  	// Error state for each APB slave

	// --------------------------------------------------------------------------
	// Internal wires
	// --------------------------------------------------------------------------

	reg  [ADDRWIDTH-3:0]   	addr_reg;    		// Address sample register
	reg                    	wr_reg;      		// Write control sample register
	reg  [2:0]  			state_reg;   		// State for finite state machine
					
	reg  [3:0]  			pstrb_reg;   		// Byte lane strobe register
	wire [3:0]  			pstrb_nxt;   		// Byte lane strobe next state
	reg  [1:0]  			pprot_reg;   		// PPROT register
	wire [1:0]  			pprot_nxt;   		// PPROT register next state
					
	wire        			apb_select;  		// APB bridge is selected
	wire        			apb_tran_end;		// Transfer is completed on APB
	reg  [2:0]  			next_state;  		// Next state for finite state machine
	reg  [31:0]   			rwdata_reg;  		// Read/Write data sample register
	
	wire 					reg_rdata_cfg; 		// REGISTER_RDATA paramater
	wire 					reg_wdata_cfg; 		// REGISTER_WDATA paramater

	reg 					sample_wdata_reg; 	// Control signal to sample HWDATA

// -------------------------------------------------------------------------
// State machine
// -------------------------------------------------------------------------

	localparam 				 ST_BITS = 3;

	localparam [ST_BITS-1:0] ST_IDLE      = 3'b000; // Idle waiting for transaction
	localparam [ST_BITS-1:0] ST_APB_WAIT  = 3'b001; // Wait APB transfer
	localparam [ST_BITS-1:0] ST_APB_TRNF  = 3'b010; // Start APB transfer
	localparam [ST_BITS-1:0] ST_APB_TRNF2 = 3'b011; // Second APB transfer cycle
	localparam [ST_BITS-1:0] ST_APB_ENDOK = 3'b100; // Ending cycle for OKAY
	localparam [ST_BITS-1:0] ST_APB_ERR1  = 3'b101; // First cycle for Error response
	localparam [ST_BITS-1:0] ST_APB_ERR2  = 3'b110; // Second cycle for Error response
	localparam [ST_BITS-1:0] ST_ILLEGAL   = 3'b111; // Illegal state

// --------------------------------------------------------------------------
// Start of main code
// --------------------------------------------------------------------------
	// Configuration signal
	assign reg_rdata_cfg = (REGISTER_RDATA==0) ? 1'b0 : 1'b1;
	assign reg_wdata_cfg = (REGISTER_WDATA==0) ? 1'b0 : 1'b1;
	
	// Generate APB bridge select
	assign apb_select = HSEL & HTRANS[1] & HREADY;
	// Generate APB transfer ended
	assign apb_tran_end = (state_reg==3'b011) & PREADY;
	
	assign pprot_nxt[0] =  HPROT[1];  // (0) Normal, (1) Privileged
	assign pprot_nxt[1] = ~HPROT[0];  // (0) Data, (1) Instruction
	
	// Byte strobe generation
	// - Only enable for write operations
	// - For word write transfers (HSIZE[1]=1), all byte strobes are 1
	// - For hword write transfers (HSIZE[0]=1), check HADDR[1]
	// - For byte write transfers, check HADDR[1:0]
	assign pstrb_nxt[0] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b00));
	assign pstrb_nxt[1] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b01));
	assign pstrb_nxt[2] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b10));
	assign pstrb_nxt[3] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b11));

	// Sample control signals
	always @(posedge HCLK or negedge HRESETn) 
	begin
		if (~HRESETn) begin
			addr_reg  <= {(ADDRWIDTH-2){1'b0}};
			wr_reg    <= 1'b0;
			pprot_reg <= {2{1'b0}};
			pstrb_reg <= {4{1'b0}};
		end
		else if (apb_select) begin// Capture transfer information at the end of AHB address phase		
			addr_reg  <= HADDR[ADDRWIDTH-1:2];
			wr_reg    <= HWRITE;
			pprot_reg <= pprot_nxt;
			pstrb_reg <= pstrb_nxt;
		end
	end

	// Sample write data control signal
	// Assert after write address phase, deassert after PCLKEN= 1
	wire sample_wdata_set = apb_select & HWRITE & reg_wdata_cfg;
	wire sample_wdata_clr = sample_wdata_reg & PCLKEN;

	always @(posedge HCLK or negedge HRESETn) 
	begin
		if (~HRESETn)
			sample_wdata_reg <= 1'b0;
		else if (sample_wdata_set | sample_wdata_clr)
			sample_wdata_reg <= sample_wdata_set;
	end

	// Generate next state for FSM
	// Note : case 3'b111 is not used.  The design has been checked that
	//        this illegal state cannot be entered using formal verification.
	always @(state_reg or PREADY or PSLVERR or apb_select or reg_rdata_cfg or
		PCLKEN or reg_wdata_cfg or HWRITE) 
	begin
		case (state_reg)
			// Idle
			ST_IDLE : begin
				if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
					next_state = ST_APB_TRNF; 		// Start APB transfer in next cycle
				else if (apb_select)
					next_state = ST_APB_WAIT;		// Wait for start of APB transfer at PCLKEN high
				else
					next_state = ST_IDLE; 			// Remain idle
			end
			// Transfer announced on AHB, but PCLKEN was low, so waiting
			ST_APB_WAIT : begin
				if (PCLKEN)
					next_state = ST_APB_TRNF; 		// Start APB transfer in next cycle
				else
					next_state = ST_APB_WAIT; 		// Wait for start of APB transfer at PCLKEN high
			end
			// First APB transfer cycle
			ST_APB_TRNF : begin
				if (PCLKEN)
					next_state = ST_APB_TRNF2;  	// Change to second cycle of APB transfer
				else
					next_state = ST_APB_TRNF;   	// Change to state-2
			end
			// Second APB transfer cycle
			ST_APB_TRNF2 : begin		
				if (PREADY & PSLVERR & PCLKEN) 				// Error received - Generate two cycle
															// Error response on AHB by
					next_state = ST_APB_ERR1; 				// Changing to state-5 and 6
				else if (PREADY & (~PSLVERR) & PCLKEN) begin // Okay received
					if (reg_rdata_cfg)
						// Registered version
						next_state = ST_APB_ENDOK; 			// Generate okay response in state 4
					else
						// Non-registered version
						next_state = {2'b00, apb_select}; 	// Terminate transfer
				end
				else 	
					// Slave not ready
					next_state = ST_APB_TRNF2; 				// Unchange
			end
			// Ending cycle for OKAY (registered response)
			ST_APB_ENDOK : begin
				if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
					next_state = ST_APB_TRNF; 	// Start APB transfer in next cycle
				else if (apb_select)
					next_state = ST_APB_WAIT; 	// Wait for start of APB transfer at PCLKEN high
				else
					next_state = ST_IDLE; 		// Remain idle
			end
			// First cycle for Error response
			ST_APB_ERR1 : 
				next_state = ST_APB_ERR2; 		// Goto 2nd cycle of error response
			// Second cycle for Error response
			ST_APB_ERR2 : begin
				if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE))
					next_state = ST_APB_TRNF; 	// Start APB transfer in next cycle
				else if (apb_select)
					next_state = ST_APB_WAIT; 	// Wait for start of APB transfer at PCLKEN high
				else
					next_state = ST_IDLE; 		// Remain idle
			end
			default : // Not used
				next_state = 3'bxxx; // X-Propagation
		endcase
	end

	// Registering state machine
	always @(posedge HCLK or negedge HRESETn)
	begin
		if (~HRESETn)
			state_reg <= 3'b000;
		else
			state_reg <= next_state;
	end

	// Sample PRDATA or HWDATA
	always @(posedge HCLK or negedge HRESETn)
	begin
		if (~HRESETn)
			rwdata_reg <= {32{1'b0}};
		else
			if (sample_wdata_reg & reg_wdata_cfg & PCLKEN)
				rwdata_reg <= HWDATA;
			else if (apb_tran_end & reg_rdata_cfg & PCLKEN)
				rwdata_reg <= PRDATA;
	end

	// Connect outputs to top level
	assign PADDR   = {addr_reg, 2'b00}; // from sample register
	assign PWRITE  = wr_reg;            // from sample register
	// From sample register or from HWDATA directly
	assign PWDATA  = (reg_wdata_cfg) ? rwdata_reg : HWDATA;
	assign PSEL    = (state_reg==ST_APB_TRNF) | (state_reg==ST_APB_TRNF2);
	assign PENABLE = (state_reg==ST_APB_TRNF2);
	assign PPROT   = {pprot_reg[1], 1'b0, pprot_reg[0]};
	assign PSTRB   = pstrb_reg[3:0];

	// Generate HREADYOUT
	always @(state_reg or reg_rdata_cfg or PREADY or PSLVERR or PCLKEN)
	begin
		case (state_reg)
			ST_IDLE      : HREADYOUT = 1'b1; // Idle
			ST_APB_WAIT  : HREADYOUT = 1'b0; // Transfer announced on AHB, but PCLKEN was low, so waiting
			ST_APB_TRNF  : HREADYOUT = 1'b0; // First APB transfer cycle
			// Second APB transfer cycle:
			// if Non-registered feedback version, and APB transfer completed without error
			// Then response with ready immediately. If registered feedback version,
			// wait until state_reg==ST_APB_ENDOK
			ST_APB_TRNF2 : HREADYOUT = (~reg_rdata_cfg) & PREADY & (~PSLVERR) & PCLKEN;
			ST_APB_ENDOK : HREADYOUT = reg_rdata_cfg; // Ending cycle for OKAY (registered response only)
			ST_APB_ERR1  : HREADYOUT = 1'b0; // First cycle for Error response
			ST_APB_ERR2  : HREADYOUT = 1'b1; // Second cycle for Error response
			default: HREADYOUT = 1'bx; // x propagation (note :3'b111 is illegal state)
		endcase
	end

	// From sample register or from PRDATA directly
	assign HRDATA = (reg_rdata_cfg) ? rwdata_reg : PRDATA;
	assign HRESP  = (state_reg==ST_APB_ERR1) | (state_reg==ST_APB_ERR2);

	assign APBACTIVE = (HSEL & HTRANS[1]) | (|state_reg);

`ifdef ARM_APB_ASSERT_ON
// ------------------------------------------------------------
// Assertions
// ------------------------------------------------------------
`include "std_ovl_defines.h"
// Capture last read APB data
	reg [31:0] ovl_last_read_apb_data_reg;

	always@(posedge HCLK or negedge HRESETn) begin
		if(~HRESETn)
			ovl_last_read_apb_data_reg <= {32{1'b0}};
		else if(PREADY & PENABLE & (~PWRITE) & PSEL & PCLKEN)
			ovl_last_read_apb_data_reg <= PRDATA;
	end
	
	//Capture last APB response, clear at start of APB transfer
	reg ovl_last_read_apb_resp_reg;
	always@(posedge HCLK or negedge HRESETn) begin
		if(~HRESETn)
			ovl_last_read_apb_resp_reg <= 1'b0;
		else begin
			if(PCLKEN) begin
				if(PREADY & PSEL)
					ovl_last_read_apb_resp_reg <= PSLVERR & PENABL;
				else
					ovl_last_read_apb_resp_reg <= 1'b0;
			end
		end
	end
	
	// Create signal to indicate data phase
	reg		ovl_ahb_data_phase_reg;
	wire	ovl_ahb_data_phase_nxt = HSEL & HTRANS[1];
	
	always@(posedge HCLK or negedge HRESETn) begin
		if(~HRESETn)
			ovl_ahb_data_phase_reg <= 1'b0;
		else if(HREADY)
			ovl_ahb_data_phase_reg <= ovl_ahb_data_phase_nxt;
	end
	
	generate 
		if(REGISTER_RDATA! = 0) begin:gen_ovl_read_data_mistmatch
		//Last read APB data should be the same as HRDATA, unless there is an
		//(Only check if using registered read data config, Otherwise HRDATA)
		//to PRDATA so no need to have OVL check
		assert_implication #(
			`OVL_ERROR,
			`OVL_ASSERT,
			"Read data mismatch")
		u_ovl_read_data_mistmatch(
			.clk(HCLK),
			.reset_n(HRESETn),
			.antecedent_expr(ovl_ahb_data_phase_reg&(~wr_reg)&HREADYOUT&(~HRESP)),
			.consequent_expr(ovl_last_read_apb_data_reg == HRDATA)
			);
		end	
	endgenerate
			
	//AHB response should match APB response
	assert_implication #(
		`OVL_ERROR, 
		`OVL_ASSERT,
		"Response mismatch")
	u_ovl_resp_mistmatch(
		.clk(HCLK),
		.reset_n(HRESETn),
		.antecedent_expr(ovl_ahb_data_phase_reg & HREADYOUT),
		.consequent_expr(ovl_last_read_apb_resp_reg == HRESP)
	);
	
	// APBACTIVE should be high before and during APB transfers
	assert_implication #(
		`OVL_ERROR, 
		`OVL_ASSERT,
		"APBACTIVE should be active when PSEL is high")
	u_ovl_apbactive_1(
		.clk(HCLK),
		.reset_n(HRESETn),
		.antecedent_expr(PSEL),
		.consequent_expr(APBACTIVE)
	);

	assert_implication #(
		`OVL_ERROR, 
		`OVL_ASSERT,
		"APBACTIVE should be active when there is an active transfer (data phase)")
	u_ovl_apbactive_2(
		.clk(HCLK),
		.reset_n(HRESETn),
		.antecedent_expr(~HREADYOUT),
		.consequent_expr(APBACTIVE)
	);
	
	assert_implication #(
		`OVL_ERROR, 
		`OVL_ASSERT,
		"APBACTIVE should be active when there is an active transfer (data phase)")
	u_ovl_apbactive_3(
		.clk(HCLK),
		.reset_n(HRESETn),
		.antecedent_expr(HSEL & HTRANS[1]),
		.consequent_expr(APBACTIVE)
	);
	
	//Create register to check a transfer on AHB side result in a transfer in APB side
	reg			ovl_transfer_started_reg;
	//Set by transfer starting, clear by PSEL
	wire		ovl_transfer_started_nxt = (HREADY & ovl_ahb_data_phase_nxt) | 
											(ovl_transfer_started_reg &(~PSEL))；
	always@(posedge HCLK or negedge HRESETn) begin
		if(~HRESETn)
			ovl_transfer_started_reg <= 1'b0;
		else
			ovl_transfer_started_reg <= ovl_transfer_started_nxt;
	end
	
	//Check a valid transfer must result in an APB transfer
	assert_never #(	`OVL_ERROR, 
					`OVL_ASSERT, 
					"APB_transfer missing.")
	u_ovl_apb_transfer_missing(
		.clk(HCLK),
		.reset_n(HRESETn),
		.test_expr(HREADY & ovl_ahb_data_phase_nxt & ovl_transfer_started_reg)
	);
	
	//Ensure state_reg must not be 3'b111;
	assert_never #(	`OVL_ERROR, 
					`OVL_ASSERT, 
					"state_reg in illegal state")
	u_ovl_apb_transfer_missing(
		.clk(HCLK),
		.reset_n(HRESETn),
		.test_expr(state_reg == ST_ILLEGAL)
	);
`endif
endmodule

